﻿/*
 * Created by SharpDevelop.
 * User: Corrado Mio
 * Date: 01/06/2012
 * Time: 05:39
 * 
 * 
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using log4net;

namespace CSharpExt.Threading
{
    /// <summary>
    /// La classe ThreadPools mette a disposizione le stesse API' di ThreadPool, ma 
    /// con alcune funzionalita' in piu':
    /// 
    /// - tiene traccia del numero di item in coda
    /// - tiene traccia dei thread in esecuzione
    /// - protegge ogni thread dalle eccezioni non gestite
    /// - aggiunge il supporto a Runnable
    /// 
    /// Inoltre aggiunge le seguenti funzionalita':
    /// 
    /// - permette di avere la lista dei thread in esecuzione
    /// - e' possibile indicare il numero massimo di item in coda, dopo di che 
    ///   vengono emmesse delle righe di log a livello WARN
    /// - semplifica il modo in cui si possono ottenere il numero minimo e massimo di 
    ///   thread che il pool puo' gestire, 
    /// 
    /// Note: un termine migliore a 'QueueUserWorkItem' potrebbe essere 'InvokeAsync'
    /// 
    /// Nota: ThreadGroup e' un'implementazione piu' intelligente, perche' permette di
    ///     tracciare solo i thread di cui si e' interessati, e non TUTTI i thread
    ///     dell'applicazione, nascondendo la presenza del 'completionPortThreads'
    /// 
    /// </summary>
    public static class ThreadPools
    {
        #region Private Data
        
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private static extern Int32 GetCurrentThreadId();
        
        private static int _maxUserWorkItems;
        private static int _waitingUserWorkItems;
        
        private static Dictionary<int, Thread> _runningThreads = new Dictionary<int, Thread>();
        
        #endregion
        
        #region Properties
        
        public static int RunningThreadsCount
        {
            get { return _runningThreads.Count; }
        }
        
        public static int AvailableThreadsCount
        {
            get { return GetAvailableThreads(); }
        }
        
        public static int WaitingUserWorkItems
        {
            get { return GetWaitingUserWorkItems(); }
        }
        
        
        public static Thread[] RunningThreads
        {
            get
            {
                lock (_runningThreads)
                {
                    return _runningThreads.Values.ToArray();
                }
            }
        }
        
        #endregion
        
        #region Advanced Operations
        
        public static int GetMaxUserWorkItems()
        {
            return _maxUserWorkItems;
        }
        
        public static void SetMaxUserWorkItems(int userWorkItems)
        {
            _maxUserWorkItems = userWorkItems;
        }
        
        public static int GetWaitingUserWorkItems()
        {
            return _waitingUserWorkItems;
        }
        
        
        public static int GetMaxThreads()
        {
            int workerThreads, completionPortThreads;
            ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
            return workerThreads;
        }
        
        public static bool SetMaxThreads(int workerThreads)
        {
            int dummy, maxCompletionPortThreads;
            ThreadPool.GetMaxThreads(out dummy, out maxCompletionPortThreads);
            return ThreadPool.SetMaxThreads(workerThreads, maxCompletionPortThreads);
        }

        
        public static int GetAvailableThreads()
        {
            int workerThreads, completionPortThreads;
            ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
            return workerThreads;
        }
        
        #endregion
        
        #region QueueUserWorkItem
        
        // -- Action()
        
        public static bool QueueUserWorkItem(Action callback)
        {
            return QueueUserWorkItem((String)null, callback);
        }
        
        public static bool QueueUserWorkItem(String n, Action callback)
        {
            enqueueItem();
            
            return ThreadPool.QueueUserWorkItem(
                (arg) => 
                {
                    try
                    {
                        enterThread(n);
                        
                        callback();
                    }
                    catch(ThreadAbortException)
                    {
                        LogManager.GetLogger("ThreadPools.Thread").ErrorFormat("Thread {0} Aborted", Thread.CurrentThread.Name);
                    }
                    catch(Exception e)
                    {
                        LogManager.GetLogger("ThreadPools.Thread").Error(e, e);
                    }
                    finally
                    {
                        exitThread();
                    }
                });
        }
        
        
        // -- Action(Object)
        
        public static bool QueueUserWorkItem(Action<Object> callback, Object state)
        {
            return QueueUserWorkItem(null, callback, state);
        }
        
        public static bool QueueUserWorkItem(String n, Action<Object> callback, Object state)
        {
            enqueueItem();
            
            return ThreadPool.QueueUserWorkItem(
                (arg) => 
                {
                    try
                    {
                        enterThread(n);
                        
                        callback(arg);
                    }
                    catch(ThreadAbortException)
                    {
                        LogManager.GetLogger("ThreadPools.Thread").ErrorFormat("Thread {0} Aborted", Thread.CurrentThread.Name);
                    }
                    catch(Exception e)
                    {
                        LogManager.GetLogger("ThreadPools.Thread").Error(e, e);
                    }
                    finally
                    {
                        exitThread();
                    }
                }, 
                state);
        }
        
        
        // -- Runnable
        
        public static bool QueueUserWorkItem(Runnable runnable)
        {
            return QueueUserWorkItem(null, runnable);
        }
        
        public static bool QueueUserWorkItem(String n, Runnable runnable)
        {
            return QueueUserWorkItem(n, callRunnable, runnable);
        }
    
        #endregion
        
        #region InvokeAsync (alternative to QueueUserWorkItem)
        
        // -- Action()
        
        public static bool InvokeAsync(Action callback)
        {
            return QueueUserWorkItem(null, callback);
        }
        
        public static bool InvokeAsync(String name, Action callback)
        {
            return QueueUserWorkItem(name, callback);
        }
        
        
        // -- Action(Object)
        
        public static bool InvokeAsync(Action<Object> callbackArg, Object state)
        {
            return QueueUserWorkItem(null, callbackArg, state);
        }
        
        public static bool InvokeAsync(String name, Action<Object> callbackArg, Object state)
        {
            return QueueUserWorkItem(name, callbackArg, state);
        }
        
        
        // -- Runnable
        
        public static bool InvokeAsync(Runnable runnable)
        {
            return QueueUserWorkItem(null, callRunnable, runnable);
        }
        
        public static bool InvokeAsync(String name, Runnable runnable)
        {
            return QueueUserWorkItem(name, callRunnable, runnable);
        }
        
        #endregion
        
        #region Implementation
        
        private static void enqueueItem()
        {
            Interlocked.Increment(ref _waitingUserWorkItems);
  
            if(_maxUserWorkItems > 0 && _waitingUserWorkItems > _maxUserWorkItems)
            {
                LogManager.GetLogger("ThreadPool").ErrorFormat("Too waiting UserWorkItems: {0} > {1}", _waitingUserWorkItems, _maxUserWorkItems);
            }
        }
        
        private static void enterThread(String name)
        {
            if (!String.IsNullOrEmpty(name))
                Thread.CurrentThread.Name = name;
                
            Interlocked.Decrement(ref _waitingUserWorkItems);
            
            lock (_runningThreads)
            {
                Thread currentThread = Thread.CurrentThread;
                int processThreadId = GetCurrentThreadId();
                _runningThreads[currentThread.ManagedThreadId] = currentThread;
            }
        }
        
        private static void exitThread()
        {
            lock (_runningThreads)
            {
                Thread currentThread = Thread.CurrentThread;
                _runningThreads.Remove(currentThread.ManagedThreadId);
            }
        }
        
        private static void callRunnable(Object arg)
        {
            ((Runnable)arg).Run();
        }
        
        #endregion
    }
}
